"""
	The MainView module defines the classes for the view of the HeaDSET programs.
	The view layout of the 3 HeaDSET programs have a common abstract View class.
	
	@author: J.H. Donker, T.L. Siaw, J.Teeuw
	@organization: University of Twente
"""

# ==================== Importing external libraries

import os, wx
from wx.lib.pubsub import Publisher
from FloatSpinner import *
from model import *
from VTKComponent import *

# ==================== Supporting classes for View

class MyStatusBar(wx.StatusBar):
    """
		MyStatusBar is an own implementation of the StatusBar from wxPython.
    """
    
    # -------------------- Constants of MyStatusBar
    
    STATUS_FIELDS = 2
    STATUS_WIDTHS = [-4, -1]
    
    def __init__(self, parent):
        """
		The constructor for this class initialises a statusbar with two fields and
		sets their widths appropiately.	This setting is used for redrawing the
		statusbar when resizing the window.

		@param parent: The parent window to which this statusbar belongs.
	"""
        
	wx.StatusBar.__init__(self, parent)
	self.SetFieldsCount(MyStatusBar.STATUS_FIELDS)
	self.SetStatusWidths(MyStatusBar.STATUS_WIDTHS)

# -------------------- End of class MyStatusBar



# ==================== GUI classes for the views

class View(wx.Frame):
        """
		View is the superclass of the 3 views of the HeaDSET programs.
		It provides the common features and layout for the subviews.
        """
        
	# -------------------- GUI Constants of View
	
	WIDTH          = 1000
	HEIGHT         = 3 * WIDTH / 4
	BUTTON_WIDTH   = 75
	L_BUTTON_WIDTH = 2 * BUTTON_WIDTH
	MIN_SIZE       = wx.Size(10 * BUTTON_WIDTH, 8 * BUTTON_WIDTH)

	FILE_EXIT   = 100
	SINGLE_VIEW = FILE_EXIT   + 1
	MULTI_VIEW  = SINGLE_VIEW + 1
	CONFIG_VIEW = MULTI_VIEW  + 1 
	HELP_ABOUT  = CONFIG_VIEW + 1
	HELP_HELP   = HELP_ABOUT  + 1
	
	EXPLANATION_TEXT = HELP_HELP        + 1
	VISUALIZER       = EXPLANATION_TEXT + 1

	def __init__(self, parent, parentId, title, model):
                """
                    The constructor for View initialises the main layout of the frame window.
                    This layout consists of a menu- and statusbar and the main panel. This
                    main panel is further divided into:
                            - a left panel for user input.
                            - a right panel for visualizing the model.
                    
                    @param parent: The parent window to which this frame window belongs.
                    @param parentId: The id given to the parent window.
                    @param title: The title given to the parent window.
                    @param model: The model belonging to this GUI.
                """
                
		wx.Frame.__init__(self, parent, parentId, "HeaDSET " + title, size = (View.WIDTH, View.HEIGHT))
		self.__icon = wx.Icon("res/HeaDSETLogo.png", wx.BITMAP_TYPE_PNG)
		self.SetIcon(self.__icon)
		self.SetMinSize(View.MIN_SIZE)

		# Initialize menubar & statusbar
		self.InitMenuBar()
		self.SetStatusBar(MyStatusBar(self))

		# Initialize main panel (consisting of user input panel & 3D visualizer panel)
		mainPanel = wx.Panel(self)
		mainSizer = wx.BoxSizer(wx.HORIZONTAL)

		# Initialize user input panel
		inputPanel = wx.Panel(mainPanel, style = wx.BORDER_SIMPLE)
		self.InitUserInput(inputPanel)
		
		# Initialize 3D visualizer panel
		visualizerPanel = wx.Panel(mainPanel)
		self.InitVisualizer(visualizerPanel, model)

                # Add panels to sizer in main panel and show frame window
		mainSizer.Add(inputPanel, 0, wx.EXPAND | wx.ALL, 5)
		mainSizer.Add(visualizerPanel, 1, wx.EXPAND)
		mainPanel.SetSizer(mainSizer)
		self.Maximize()
		self.Show(True)

	# -------------------- Methods for initializing the menubar
	
	def InitMenuBar(self):
                """
                    This method initializes the menubar of the frame window with menus
                    and menu items. Here only the common menus are initialized, namely
                    File, Viewports and Help menus.
                """
                
		menuBar = wx.MenuBar()

		# Initializing File menu with at least Exit menu item
		fileMenu = wx.Menu()
		self.InitFileMenu(fileMenu)
		exitMI   = wx.MenuItem(fileMenu, View.FILE_EXIT, "E&xit\tCtrl+Q")
		
		fileMenu.AppendSeparator()
		fileMenu.AppendItem(exitMI)
		menuBar.Append(fileMenu, "&File")

		# Initializing other menus (if necessary)
		self.InitOtherMenus(menuBar)
		
		# Initializing Viewports menu with the corresponding menu items
		viewMenu = wx.Menu()
		singleViewMI = viewMenu.AppendRadioItem(View.SINGLE_VIEW, "&Single Viewport")
		multiViewMI  = viewMenu.AppendRadioItem(View.MULTI_VIEW , "&Multiple Viewports")

		configViewMI = wx.MenuItem(viewMenu, View.CONFIG_VIEW, "&Configure Viewports...")
		
		viewMenu.AppendSeparator()
		viewMenu.AppendItem(configViewMI)
		menuBar.Append(viewMenu, "&Viewports")
		
		# Initializing Help menu with at least About and Help menu items
		helpMenu = wx.Menu()
		aboutMI  = wx.MenuItem(helpMenu, View.HELP_ABOUT, "&About " + self.GetTitle() + "...")
		helpMI   = wx.MenuItem(helpMenu, View.HELP_HELP , self.GetTitle() + " &Help...\tF1")
		
		helpMenu.AppendItem(aboutMI)
		helpMenu.AppendSeparator()
		helpMenu.AppendItem(helpMI)
		menuBar.Append(helpMenu, "&Help")
		
		self.SetMenuBar(menuBar)

	def InitFileMenu(self, fileMenu):
                """
                    This is an abstract method used for initializing optional menu items in File menu.
                    In subclasses of View this method can be overwritten as follows:
                            - First create one or more new menu items.
                            - Second append the menu items to the File menu (provided as parameter).
                    
                    @param fileMenu: The File menu to which menu items can be appended.
                """
                
		pass

	def InitOtherMenus(self, menuBar):
                """
                    This is an abstract method used for initializing optional other menus
                    and their menu items between the File menu and the ViewPorts menu.
                    In subclasses of View this method can be overwritten as follows:
                            - First create one or more new menus.
                            - Second create menu items and append these to the new menus.
                            - Third append the menus to the menubar (provided as parameter).
                    
                    @param menuBar: The menubar to which new menus can be appended.
                """
                
		pass

	# -------------------- Methods for initializing the user input panel

	def InitUserInput(self, inputPanel):
                """
                    This is an abstract method used for initializing the user input panel.
                    This panel is located on the left side of the frame window.
                    In subclasses of View this method can be overwritten as follows:
                            - Use the input panel (provided as parameter), as the panel to put widgets in it.
                              It would be useful to define a sizer and attach it to this panel.
                            - If one or more subpanels are used, then the input panel (provided as parameter),
                              must be the parent on top of all the other panels.
                    
                    @param inputPanel: The panel to which a sizer and other widgets can be attached.
                """
                
		pass

	def InitExplanationPanel(self, parent):
                """
                    This method initializes an explanation panel used for putting
                    information about other user input panels (to show how for example input fields work).
                    This method should be called in subclasses of View and preferably in the method InitUserInput.
                    
                    @param parent: The parent window to which the newly created explanation panel belongs.
                    @return: wx.Panel object, the newly created explanation panel.
                """
                
		explanationPanel = wx.Panel(parent)
		explanationSizer = wx.StaticBoxSizer(wx.StaticBox(explanationPanel, -1, "Explanation"), wx.VERTICAL)
		
		labelExplanation = wx.StaticText(explanationPanel, View.EXPLANATION_TEXT)
		
		explanationSizer.Add(labelExplanation, 0, wx.EXPAND | wx.ALL, 5)
		explanationPanel.SetSizer(explanationSizer)
		return explanationPanel

	# -------------------- Methods for initializing the visualizer panel

	def InitVisualizer(self, parent, model):
                """
                    This method initializes a visualizer panel used for showing the 3D model.
                    The visualizer panel also contains a bar panel at the bottom and is initially empty.
                    
                    @param parent: The parent panel to which a sizer and other widgets can be attached.
                    @param model: The model belonging to this GUI.
                """
                
		visualizerSizer = wx.BoxSizer(wx.VERTICAL)

		vtkModelPanel = VTKComponent(model, parent, View.VISUALIZER)
		barPanel = self.InitBarVisualizer(parent)
		
		visualizerSizer.Add(vtkModelPanel, 1, wx.EXPAND)
		visualizerSizer.Add(barPanel     , 0, wx.EXPAND)
		parent.SetSizer(visualizerSizer)

	def InitBarVisualizer(self, parent):
                """
                    This is an abstract method used for initializing the bar panel at the bottom
                    of the visualizer panel. This panel can be used to put additional widgets.
                    In subclasses of View this method can be overwritten as follows:
                            - Create a new panel to put a sizer and other widgets in it. One should notice
                              that the bar is just a strip with a size of approximately (30 x width visualizer panel).
                            - The newly created panel has as parent the parent panel (provided as argument).
                    
                    @param parent: The parent window to which the newly created bar panel belongs.
                    @return: wx.Panel object, the newly created bar panel.
                """
                
		barPanel = wx.Panel(parent)
		barPanel.Show(False)
		return barPanel

	# -------------------- Methods for updating the frame window (after user input)

	def ShowExplanation(self, info):
                """
                    This method updates the explanation panel with textual information.
                    
                    @param info: The (textual) information to be shown in the explanation panel.
                """
                
		textLabel = self.FindWindowById(View.EXPLANATION_TEXT)
		textLabel.SetLabel(info)
		textLabel.Wrap(View.L_BUTTON_WIDTH)

	def SettingStatusBar(self, message1, message2):
                """
                    This method updates the fields in the statusbar of the frame window.
                    
                    @param message1: The message to be shown in the left field of the statusbar.
                    @param message2: The message to be shown in the rightt field of the statusbar.
                """
                
		self.SetStatusText(message1, 0)
		self.SetStatusText(message2, 1)

# -------------------- End of class View



class ModelingView(View):
        """
		ModelingView is a subclass of View and provides the layout
		for the HeaDSET program PathCreator. It provides the GUI layout
		for indicating tumors and safe paths in a Dicom model.
        """

	# -------------------- GUI Constants of ModelingView
	
	FILE_NEW    = View.FILE_EXIT + 100
	FILE_OPEN   = FILE_NEW       + 1
	FILE_SAVE   = FILE_OPEN      + 1
	FILE_SAVEAS = FILE_SAVE      + 1
	FILE_IMPORT = FILE_SAVEAS    + 1
	FILE_PROP   = FILE_IMPORT    + 1

	MOD_PANEL   = FILE_NEW  + 100
	MOD_TABS    = MOD_PANEL + 1
	BO_BUTTON   = MOD_TABS  + 1
	EL_BUTTON   = BO_BUTTON + 1
	CY_BUTTON   = EL_BUTTON + 1
	CO_BUTTON   = CY_BUTTON + 1

	POS_PANEL   = MOD_PANEL + 100
	POS_TABS    = POS_PANEL + 1
	PRIM_TEXT   = POS_TABS  + 1
	X_LABEL     = PRIM_TEXT + 1
	Y_LABEL	    = X_LABEL   + 1
	Z_LABEL	    = Y_LABEL   + 1
	
	MODELINFO_TAB = POS_PANEL     + 100
	DICOM_INFO    = MODELINFO_TAB + 1
	TUMOR_INFO    = DICOM_INFO    + 1
	SAFE_INFO     = TUMOR_INFO    + 1

	MODEL_BAR   = MODELINFO_TAB + 100
	CLO_BUTTON  = MODEL_BAR     + 1
	CLR_BUTTON  = CLO_BUTTON    + 1
	DEL_BUTTON  = CLR_BUTTON    + 1

	NR_SCALE    = 0
	NR_MOVE     = 1
	NR_ROTATE   = 2
	
	X_SPIN = MODEL_BAR + 100
	Y_SPIN = X_SPIN    + 10
	Z_SPIN = Y_SPIN    + 10

	SCALE_MSG   = "... following parameters:"
	MOVE_MSG    = "... following coordinate:"
	ROTATE_MSG  = "... about following axes:"

	def __init__(self, model):
                """
                    The constructor for ModelingView initialises the layout of the frame window
                    for the HeaDSET program PathCreator.
                    
                    @param model: The model belonging to this GUI.
                """
                
		View.__init__(self, None, -1, "PathCreator", model)
		self.lastSelectedLabel = None
		self.StartNew()

                Publisher.subscribe(self.OnAdded  , "DicomAdded")
                Publisher.subscribe(self.OnRemoved, "DicomRemoved")
		Publisher.subscribe(self.OnAdded  , "GeomAdded")
		Publisher.subscribe(self.OnRemoved, "GeomRemoved")

	def StartNew(self):
                """
                    Initializes the GUI layout and its parameters of this View when
                    a new project is created (at program startup or when user selected this option).
                """
                
                self.infoLabelsMap = {}
		self.EmptyLastScale()
		self.ShowExplanation("")
		self.SettingStatusBar("Started new project", "")

	def EmptyLastScale(self):
                """
                    Method for setting the last changed value for the scale empty.
                """

                self.lastScale = ""

	def GetTabLabel(self, geom):
                """
                    Method for returning the corresponding label for a given primitive.

                    @param geom: The primitive for which the corresponding label will be returned.
                    @return: wx.StaticText object, the label corresponding to the primitive.
                """

		return self.infoLabelsMap[geom]

	# -------------------- Methods for initializing the menubar

	def InitFileMenu(self, fileMenu):
                """
                    This method (from View) is overwritten to initialize specific menu items in File menu.
                    Menu items are appended for creating, opening and saving a project;
                    importing a Dicom image and showing properties of the current project.
                    
                    @param fileMenu: The File menu to which menu items are appended.
                """
                
		newMI	 = wx.MenuItem(fileMenu, ModelingView.FILE_NEW   , "&New Project\tCtrl+N")
		openMI	 = wx.MenuItem(fileMenu, ModelingView.FILE_OPEN  , "&Open Project...\tCtrl+O")
		saveMI	 = wx.MenuItem(fileMenu, ModelingView.FILE_SAVE  , "&Save Project\tCtrl+S")
		saveAsMI = wx.MenuItem(fileMenu, ModelingView.FILE_SAVEAS, "Save Project &As...\tCtrl+Shift+S")
		importMI = wx.MenuItem(fileMenu, ModelingView.FILE_IMPORT, "&Import DICOM image...\tCtrl+I")
		propMI	 = wx.MenuItem(fileMenu, ModelingView.FILE_PROP  , "&Properties Project...\tCtrl+D")
		
		fileMenu.AppendItem(newMI)
		fileMenu.AppendItem(openMI)
		fileMenu.AppendSeparator()
		fileMenu.AppendItem(saveMI)
		fileMenu.AppendItem(saveAsMI)
		fileMenu.AppendSeparator()
		fileMenu.AppendItem(importMI)
		fileMenu.AppendSeparator()
		fileMenu.AppendItem(propMI)

	# -------------------- Methods for initializing the user input panel

	def InitUserInput(self, inputPanel):
                """
                    This method (from View) is overwritten to initialize the user input panel.
                    This panel has two tabs:
                        - one with panels for adding primitives to the model
                          and changing their positions (named Modeling Tools).
                        - one for showing the added components in the project (named Model Information).
                    
                    @param inputPanel: The panel to which a sizer and other widgets are attached.
                """
                
		inputSizer = wx.BoxSizer(wx.VERTICAL)
		inputNotebook = wx.Notebook(inputPanel, -1, style = wx.NB_LEFT)
		tabIcons = wx.ImageList(20, 20)
		tabIcons.AddIcon(wx.Icon("res/ModelInfo.png", wx.BITMAP_TYPE_PNG))
		tabIcons.AddIcon(wx.Icon("res/ModelTool.png", wx.BITMAP_TYPE_PNG))
		inputNotebook.AssignImageList(tabIcons)
		
		tabModelUserInput = self.InitModelUserInputTab(inputNotebook)
		tabModelInfo = self.InitModelInfoTab(inputNotebook)
		inputNotebook.AddPage(tabModelUserInput, "Modeling Tools"   , True , 0)
		inputNotebook.AddPage(tabModelInfo     , "Model Information", False, 1)

		inputSizer.Add(inputNotebook, 1, wx.EXPAND)
		inputPanel.SetSizer(inputSizer)

	# -------------------- Methods for initializing the model user input tab (part of user input panel)

	def InitModelUserInputTab(self, parent):
                """
                    This method initializes the model user input tab
                    (tab in user input panel named Modeling Tools).
                    This panel contains the following three panels:
                        - a panel for adding primitives to the model.
                        - a panel for changing the positions of a primitive in the model.
                        - a panel for showing explanations how to use the two panels mentioned above.
                    
                    @param parent: The parent window to which the newly created panel belongs.
                    @return: wx.Panel object, the newly created panel.
                """
                
		tabPanel = wx.Panel(parent)
		miPanel  = self.InitModelInputPanel(tabPanel)
		piPanel  = self.InitPosInputPanel(tabPanel)
		expPanel = self.InitExplanationPanel(tabPanel)

		tabSizer = wx.BoxSizer(wx.VERTICAL)
		tabSizer.Add(miPanel , 0, wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, 5)
		tabSizer.Add(piPanel , 0, wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, 5)
		tabSizer.Add(expPanel, 1, wx.EXPAND | wx.ALL, 5)
		tabPanel.SetSizer(tabSizer)
		return tabPanel

	def InitModelInputPanel(self, parent):
                """
                    This method initializes the model input panel (named Adding primitives)
                    inside the model user input tab (tab in user input panel named Modeling Tools).
                    It contains two tabs for adding either a tumor or a safe path primitive.
                    
                    @param parent: The parent window to which the newly created panel belongs.
                    @return: wx.Panel object, the newly created panel.
                """
                
		modelInputPanel = wx.Panel(parent, ModelingView.MOD_PANEL)
		modelInputSizer = wx.StaticBoxSizer(wx.StaticBox(modelInputPanel, -1, "Adding primitives"), wx.VERTICAL)

		tabsModel = wx.Notebook(modelInputPanel, ModelingView.MOD_TABS)
		tabTumor = self.InitModelTab(tabsModel)
		tabSafePath = self.InitModelTab(tabsModel)
		tabsModel.AddPage(tabTumor   , HeaDSETModel.TUMOR, True)
		tabsModel.AddPage(tabSafePath, HeaDSETModel.PATH)
			
		modelInputSizer.Add(tabsModel)
		modelInputPanel.SetSizer(modelInputSizer)
		return modelInputPanel

	def InitModelTab(self, tabsModel):
                """
                    This method initializes the tab in the model input panel (named Adding primitives)
                    inside the model user input tab (tab in user input panel named Modeling Tools).
                    It contains four buttons for the four types of primitives (box, ellipsoid, cylinder and cone).
                    
                    @param tabsModel: The parent window to which the newly created tab panel belongs.
                    @return: wx.Panel object, the newly created tab panel.
                """
                
		tabModelPanel = wx.Panel(tabsModel)
		tabModelSizer = wx.GridSizer(2, 2)
		
		boButton = wx.Button(tabModelPanel, ModelingView.BO_BUTTON, HeaDSETGeom.BOX	 , size = (View.BUTTON_WIDTH, -1))
		elButton = wx.Button(tabModelPanel, ModelingView.EL_BUTTON, HeaDSETGeom.ELLIPSOID, size = (View.BUTTON_WIDTH, -1))
		cyButton = wx.Button(tabModelPanel, ModelingView.CY_BUTTON, HeaDSETGeom.CYLINDER , size = (View.BUTTON_WIDTH, -1))
		coButton = wx.Button(tabModelPanel, ModelingView.CO_BUTTON, HeaDSETGeom.CONE	 , size = (View.BUTTON_WIDTH, -1))
		
		tabModelSizer.Add(boButton, 0, wx.TOP | wx.LEFT , 2)
		tabModelSizer.Add(elButton, 0, wx.TOP | wx.RIGHT, 2)
		tabModelSizer.Add(cyButton, 0, wx.BOTTOM | wx.LEFT , 2)
		tabModelSizer.Add(coButton, 0, wx.BOTTOM | wx.RIGHT, 2)

		tabModelPanel.SetSizer(tabModelSizer)
		return tabModelPanel

	def InitPosInputPanel(self, parent):
                """
                    This method initializes the positioning input panel (named Positioning primitives)
                    inside the model user input tab (tab in user input panel named Modeling Tools).
                    It contains a label and three tabs for scaling, moving and rotation.
                    
                    @param parent: The parent window to which the newly created panel belongs.
                    @return: wx.Panel object, the newly created panel.
                """
                
		posInputPanel = wx.Panel(parent, ModelingView.POS_PANEL)
		posInputSizer = wx.StaticBoxSizer(wx.StaticBox(posInputPanel, -1, "Positioning primitives"), wx.VERTICAL)

		primitiveText = wx.StaticText(posInputPanel, ModelingView.PRIM_TEXT, style = wx.ALIGN_CENTER)
		primitiveText.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL))
		primitiveText.SetForegroundColour("WHITE")

		tabsPosition = wx.Notebook(posInputPanel, ModelingView.POS_TABS)
		tabScale  = self.InitPositionTab(tabsPosition, ModelingView.SCALE_MSG)
		tabMove   = self.InitPositionTab(tabsPosition, ModelingView.MOVE_MSG)
		tabRotate = self.InitPositionTab(tabsPosition, ModelingView.ROTATE_MSG)
		tabsPosition.AddPage(tabScale , "Scale", True)
		tabsPosition.AddPage(tabMove  , "Move To")
		tabsPosition.AddPage(tabRotate, "Rotate")

		posInputSizer.Add(primitiveText, 0, wx.EXPAND | wx.BOTTOM, 2)
		posInputSizer.Add(tabsPosition , 0, wx.EXPAND)
		posInputPanel.SetSizer(posInputSizer)
		posInputPanel.Enable(False)
		return posInputPanel

	def InitPositionTab(self, tabsPosition, infoText):
                """
                    This method initializes the tab in the positioning input panel (named Positioning primitives)
                    inside the model user input tab (tab in user input panel named Modeling Tools).
                    It creates the appropiate tab for either scaling, moving or rotation and
                    contains floating point spin controls for the three coordinate axes.
                    
                    @param tabsPosition: The parent window to which the newly created tab panel belongs.
                    @param infoText: The leading message in the tab panel.
                    @return: wx.Panel object, the newly created tab panel.
                """
                
		tabPositionPanel = wx.Panel(tabsPosition)
		tabPositionSizer = wx.FlexGridSizer(2, 1, 2, 0)
		tabPositionSizer.SetFlexibleDirection(wx.BOTH)

		infoLabel = wx.StaticText(tabPositionPanel, -1, infoText)

		ctrlSizer = wx.FlexGridSizer(3, 2, 2, 2)
		ctrlSizer.SetFlexibleDirection(wx.HORIZONTAL)

		labelIds = (-1, -1, -1)
		min, max = (-180, 180)
		add = ModelingView.NR_ROTATE;
		if infoText == ModelingView.SCALE_MSG:
			labelIds = (ModelingView.X_LABEL, ModelingView.Y_LABEL, ModelingView.Z_LABEL)
			min, max = (0, 100000)
			add = ModelingView.NR_SCALE
		elif infoText == ModelingView.MOVE_MSG:
			min, max = (-1000000, 1000000)
			add = ModelingView.NR_MOVE

		textLabels = []
		textTuple = ("X:", "Y:", "Z:")
		for i in range(len(labelIds)):
			textLabels.append(wx.StaticText(tabPositionPanel, labelIds[i], textTuple[i]))
		
		ctrlSizer.Add(textLabels[0], 0, wx.TOP, 2)
		flt0 = FloatSpinner(tabPositionPanel, ModelingView.X_SPIN + add)
		flt0.SetRange(min, max)
		flt0.SetPrecision(2)
		flt0.SetResolution(0.1)
		ctrlSizer.Add(flt0)
		
		ctrlSizer.Add(textLabels[1], 0, wx.TOP, 2)
		flt1 = FloatSpinner(tabPositionPanel, ModelingView.Y_SPIN + add)
		flt1.SetPrecision(2)
		flt1.SetRange(min, max)
		flt1.SetResolution(0.1)
		ctrlSizer.Add(flt1)
		
		ctrlSizer.Add(textLabels[2], 0, wx.TOP, 2)
		flt2 = FloatSpinner(tabPositionPanel, ModelingView.Z_SPIN + add)
		flt2.SetPrecision(2)
		flt2.SetResolution(0.1)
		flt2.SetRange(min, max)
		ctrlSizer.Add(flt2)

		tabPositionSizer.Add(infoLabel, 0, wx.TOP | wx.LEFT, 2)
		tabPositionSizer.Add(ctrlSizer, 0, wx.LEFT, 2)
		tabPositionPanel.SetSizer(tabPositionSizer)
		return tabPositionPanel

	# -------------------- Methods for initializing the model info tab (part of user input panel)

	def InitModelInfoTab(self, parent):
                """
                    This method initializes the model info tab (tab in user input panel named Model Information).
                    This panel initially contains an empty layout, showing the places to be filled with
                    labels about the components (Dicom image, tumor and safe path primitives) added to the model.
                    
                    @param parent: The parent window to which the newly created panel belongs.
                    @return: wx.Panel object, the newly created panel.
                """
                
		infoPanel = wx.ScrolledWindow(parent, ModelingView.MODELINFO_TAB, style = wx.VSCROLL)
		infoSizer = wx.BoxSizer(wx.VERTICAL)

		dicomPanel = wx.Panel(infoPanel, ModelingView.DICOM_INFO)
		dicomSizer = wx.BoxSizer(wx.VERTICAL)

		dLabel = wx.StaticText(dicomPanel, -1, "DICOM Image", size = (View.L_BUTTON_WIDTH, -1), style = wx.ALIGN_CENTER)
		dicomSizer.Add(dLabel, 0, wx.ALIGN_CENTER | wx.BOTTOM, 5)
		dicomPanel.SetSizer(dicomSizer)

		tumorPanel = wx.Panel(infoPanel, ModelingView.TUMOR_INFO)
		tumorSizer = wx.BoxSizer(wx.VERTICAL)

		tLabel = wx.StaticText(tumorPanel, -1, "Tumor primitives", size = (View.L_BUTTON_WIDTH, -1), style = wx.ALIGN_CENTER)
		tumorSizer.Add(wx.StaticLine(tumorPanel), 0, wx.ALIGN_CENTER | wx.EXPAND)
		tumorSizer.Add(tLabel, 0, wx.ALIGN_CENTER | wx.BOTTOM, 5)
		tumorPanel.SetSizer(tumorSizer)

		safePanel = wx.Panel(infoPanel, ModelingView.SAFE_INFO)
		safeSizer = wx.BoxSizer(wx.VERTICAL)

		sLabel = wx.StaticText(safePanel, -1, "Safe path primitives", size = (View.L_BUTTON_WIDTH, -1), style = wx.ALIGN_CENTER)
		safeSizer.Add(wx.StaticLine(safePanel), 0, wx.ALIGN_CENTER | wx.EXPAND)
		safeSizer.Add(sLabel, 0, wx.ALIGN_CENTER | wx.BOTTOM, 5)
		safePanel.SetSizer(safeSizer)

		infoSizer.Add(dicomPanel, 0, wx.ALIGN_CENTER | wx.ALL, 5)
		infoSizer.Add(tumorPanel, 0, wx.ALIGN_CENTER | wx.ALL, 5)
		infoSizer.Add(safePanel , 0, wx.ALIGN_CENTER | wx.ALL, 5)
		infoPanel.SetSizer(infoSizer)
		
		infoPanel.SetScrollRate(20, 20)
		return infoPanel
		
	# -------------------- Methods for initializing the visualizer panel
	
	def InitBarVisualizer(self, parent):
                """
                    This method (from View) is overwritten to initialize the bar panel
                    at the bottom of the visualizer panel. It contains three buttons for
                    cloning, clearing and deleting a selected primitive.
                    
                    @param parent: The parent window to which the newly created bar panel belongs.
                    @return: wx.Panel object, the newly created bar panel.
                """
                
		barPanel = wx.Panel(parent, ModelingView.MODEL_BAR)
		barSizer = wx.BoxSizer(wx.HORIZONTAL)

                cloButton = wx.Button(barPanel, ModelingView.CLO_BUTTON, "Clone selection" , size = (View.L_BUTTON_WIDTH, -1))
		clrButton = wx.Button(barPanel, ModelingView.CLR_BUTTON, "Clear selection" , size = (View.L_BUTTON_WIDTH, -1))
		delButton = wx.Button(barPanel, ModelingView.DEL_BUTTON, "Delete selection", size = (View.L_BUTTON_WIDTH, -1))
		cloButton.Enable(False)
		clrButton.Enable(False)
		delButton.Enable(False)
		
		barSizer.Add(cloButton, 0)
		barSizer.Add(clrButton, 0)
		barSizer.Add(delButton, 0)
		barPanel.SetSizer(barSizer)
		return barPanel

	# -------------------- Methods for updating the frame window (after user input)

	def InsertInfo(self, geom):
                """
                    This method creates a new label after a new primitive haBUTTON_WIDTHs been added to the model.
                    The label will be appended in the model info tab (tab in user input panel named Model Information)
                    under the correct heading (DICOM image, Tumor primitives or Safe path primitives).
                    
                    @param geom: The newly added primitive to the model.
                """
                
		panelNr = ModelingView.TUMOR_INFO
		geomID = str(geom.GetID())
		geomInfo = " " + geomID
		labelBorder = wx.BORDER_RAISED
		labelColour = "WHITE"
		
		selectedTab = self.FindWindowById(ModelingView.MOD_TABS)
		isDicom = (geom.GetLabel() == geom.DICOM)
		if isDicom:
			panelNr = ModelingView.DICOM_INFO
			geomInfo = "\n" + geom.GetPatientName()
			labelBorder = wx.BORDER_SIMPLE
			labelColour = wx.NullColour
		elif selectedTab.GetPageText(selectedTab.GetSelection()) == HeaDSETModel.PATH:
			panelNr = ModelingView.SAFE_INFO

		tabPanel = self.FindWindowById(panelNr)
		tabSizer = tabPanel.GetSizer()

		labelText = geom.GetLabel() + geomInfo
		newLabel = wx.StaticText(tabPanel, -1, labelText, size = (View.L_BUTTON_WIDTH, -1), style = wx.ALIGN_CENTER | labelBorder, name = geomID)
		newLabel.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL))
		newLabel.SetBackgroundColour(labelColour)
		newLabel.Wrap(View.L_BUTTON_WIDTH)
		tabSizer.Add(newLabel, 0, wx.ALIGN_CENTER | wx.LEFT, 5)
		self.infoLabelsMap[geom] = newLabel

                mainTabSizer = tabPanel.GetContainingSizer()
		mainTabSizer.Layout()
		mainTabPanel = tabPanel.GetParent()
		mainTabPanel.FitInside()

		msg = "Added DICOM image to model"
		if not isDicom:
			Publisher.sendMessage("BIND_LABEL", newLabel)
			msg = "Added new primitive to model named: " + labelText
		self.SettingStatusBar(msg, "")

	def DeleteInfo(self, geom):
                """
                    This method extracts the label corresponding to the provided primitive
                    from the model info tab (tab in user input panel named Model Information)
                    and removes it from this tab.
                    
                    @param geom: The primitive removed from the model.
                """
                
		self.EnableInfoLabel(None, "", "")
		
		textLabel = self.infoLabelsMap.pop(geom)
		tabSizer = textLabel.GetContainingSizer()
		
		tabSizer.Remove(textLabel)
		nameGeom = textLabel.GetLabel()
		textLabel.Destroy()
		
		mainTabPanel = self.FindWindowById(ModelingView.MODELINFO_TAB)
		mainTabSizer = mainTabPanel.GetSizer()
		mainTabSizer.Layout()
		mainTabPanel.FitInside()

                msg = "Deleted primitive from model named: " + nameGeom
                if geom.GetLabel() == geom.DICOM:
                        msg = "Deleted DICOM image from model"
                self.SettingStatusBar(msg, "")

	def EnableInfoLabel(self, selection, typeText, primText):
                """
                    This method enables or disables the right label in model info tab named Model Information.
                    A previously enabled label should in this case be disabled (only one label at a time can be enabled).
                    
                    @param selection: The label corresponding to the selected primitive.
                    @param typeText: The type of primitive selected (tumor or safe path).
                    @param primText: The name of the selected primitive.
                    @return: True if there has been a change in label highlighting and the new selection is not empty.
                             Otherwise it is False.
                """
                
		changedLabelStatus = False
		if self.lastSelectedLabel == None:
			if selection != None:
				selection.SetBackgroundColour("NAVY")
				selection.SetForegroundColour("WHITE")
				self.Refresh()
				changedLabelStatus = True
			self.lastSelectedLabel = selection
		elif self.lastSelectedLabel != selection:
			self.lastSelectedLabel.SetBackgroundColour("WHITE")
			self.lastSelectedLabel.SetForegroundColour("BLACK")
			if selection != None:
				selection.SetBackgroundColour("NAVY")
				selection.SetForegroundColour("WHITE")
			self.Refresh()
			changedLabelStatus = True
			self.lastSelectedLabel = selection

		enable = selection != None
		if changedLabelStatus:
			self.EmptyLastScale()
			self.EnablePos(enable, typeText, primText)
		return changedLabelStatus and enable

	def EnablePos(self, enable, typeText, primText):
                """
                    This method enables or disables the panel named Positioning primitives.
                    If a primitive is selected, then this panel will be enabled. Otherwise
                    it is disabled (because there is no primitive selected to change its position).
                    
                    @param enable: Parameter indicating if the panel should be enabled or disabled.
                    @param typeText: The type of primitive selected (tumor or safe path).
                    @param primText: The name of the selected primitive.
                """
                
		textBackColour = wx.NullColour
		msg = ""
		if enable:
			textBackColour = "NAVY"
			msg = ": " + primText
		else:
			for add in range(3):
				xSpin = self.FindWindowById(ModelingView.X_SPIN + add)
				ySpin = self.FindWindowById(ModelingView.Y_SPIN + add)
				zSpin = self.FindWindowById(ModelingView.Z_SPIN + add)
				xSpin.SetValue(0)
				ySpin.SetValue(0)
				zSpin.SetValue(0)
		
		tabsPos = self.FindWindowById(ModelingView.POS_TABS)
		tabsPos.ChangeSelection(0)

		primitiveText = self.FindWindowById(ModelingView.PRIM_TEXT)
		primitiveText.SetBackgroundColour(textBackColour)
		primitiveText.SetLabel(typeText + msg)
		primitiveText.GetContainingSizer().Layout()

		labels = ("X:", "Y:", "Z:")
		if primText.startswith(HeaDSETGeom.BOX):
			labels = ("X (s):", "Y (s):", "Z (s):")
		elif primText.startswith(HeaDSETGeom.ELLIPSOID):
			labels = ("X (r):", "Y (r):", "Z (r):")
		elif primText.startswith(HeaDSETGeom.CYLINDER):
			labels = ("X (r):", "Y (r):", "Z (h):")
		elif primText.startswith(HeaDSETGeom.CONE):
			labels = ("X (h):", "Y (r):", "Z (r):")

		xLabel = self.FindWindowById(ModelingView.X_LABEL)
		yLabel = self.FindWindowById(ModelingView.Y_LABEL)
		zLabel = self.FindWindowById(ModelingView.Z_LABEL)
		xLabel.SetLabel(labels[0])
		yLabel.SetLabel(labels[1])
		zLabel.SetLabel(labels[2])

		posPanel = self.FindWindowById(ModelingView.POS_PANEL)
		xLabel.GetContainingSizer().Layout()
		posPanel.Enable(enable)
		self.FindWindowById(ModelingView.CLO_BUTTON).Enable(enable)
		self.FindWindowById(ModelingView.DEL_BUTTON).Enable(enable)
		self.FindWindowById(ModelingView.CLR_BUTTON).Enable(enable)
		self.ShowExplanation("")

	def UpdatePos(self, geom, setStatus):
                """
                    This method updates the values in the spin controls in the panel named Positioning primitives.
                    
                    @param geom: The primitive whose scale, orientation or position has been changed.
                    @param setStatus: Parameter indicating if the statusbar of the frame window should be updated or not.
                """
                
		positioningType = ""
		xyzCoordinate = ""

		x,z,y = (0,0,0)
		
		x,z,y = geom.GetScale()	
		add = ModelingView.NR_SCALE
		xSpin = self.FindWindowById(ModelingView.X_SPIN + add)
		ySpin = self.FindWindowById(ModelingView.Y_SPIN + add)
		zSpin = self.FindWindowById(ModelingView.Z_SPIN + add)

		firstTimeSelected = (xSpin.GetValue(), ySpin.GetValue(), zSpin.GetValue()) == (0.0, 0.0, 0.0)
		print self.lastScale
		print " (%.3f, %.3f, %.3f)" % (x, y, z)

		if self.lastScale != " (%.3f, %.3f, %.3f)" % (x, y, z):
		    xSpin.SetValue(x)
		    ySpin.SetValue(y)
		    zSpin.SetValue(z)
		    positioningType = "Scaled selected primitive"
		    xyzCoordinate = " (%.2f, %.2f, %.2f)" % (xSpin.GetValue(), ySpin.GetValue(), zSpin.GetValue())
		    self.lastScale = " (%.3f, %.3f, %.3f)" % (x, y, z)
		    print self.lastScale, " s"

		x,z,y = geom.GetPosition()
		add = ModelingView.NR_MOVE
		xSpin = self.FindWindowById(ModelingView.X_SPIN + add)
		ySpin = self.FindWindowById(ModelingView.Y_SPIN + add)
		zSpin = self.FindWindowById(ModelingView.Z_SPIN + add)

		if " (%.2f, %.2f, %.2f)" % (x, y, z) != " (%.2f, %.2f, %.2f)" % (xSpin.GetValue(), ySpin.GetValue(), zSpin.GetValue()):
		    xSpin.SetValue(x)
		    ySpin.SetValue(y)
		    zSpin.SetValue(z)
		    print " (%.3f, %.3f, %.3f) m" % (x, y, z)
		    if positioningType == "":
			positioningType = "Moved selected primitive"
			xyzCoordinate = " (%.2f, %.2f, %.2f)" % (xSpin.GetValue(), ySpin.GetValue(), zSpin.GetValue())
		
		x,z,y = geom.GetOrientation()
		add = ModelingView.NR_ROTATE
		xSpin = self.FindWindowById(ModelingView.X_SPIN + add)
		ySpin = self.FindWindowById(ModelingView.Y_SPIN + add)
		zSpin = self.FindWindowById(ModelingView.Z_SPIN + add)

		if " (%.2f, %.2f, %.2f)" % (x, y, z) != " (%.2f, %.2f, %.2f)" % (xSpin.GetValue(), ySpin.GetValue(), zSpin.GetValue()):
		    xSpin.SetValue(x)
		    ySpin.SetValue(y)
		    zSpin.SetValue(z)
		    print " (%.3f, %.3f, %.3f) r" % (x, y, z)
		    positioningType = "Rotated selected primitive"
		    xyzCoordinate = " (%.2f, %.2f, %.2f)" % (xSpin.GetValue(), ySpin.GetValue(), zSpin.GetValue())

		if not firstTimeSelected and setStatus:
		    self.SettingStatusBar(positioningType, xyzCoordinate)

	# -------------------- Publisher-Subscriber methods

	def OnAdded(self, message):
                """
                    Method indicating that a new primitive has been added to the model and
                    used to update this View accordingly.
                    
                    @param message: The data provided to this View to make the necessary changes possible.
                """

		self.EmptyLastScale()
		geom = message.data
		self.InsertInfo(geom)
	
	def OnRemoved(self, message):
                """
                    Method indicating that a primitive has been removed from the model and
                    used to update this View accordingly.
                    
                    @param message: The data provided to this View to make the necessary changes possible.
                """
                
		self.EmptyLastScale()
		geom = message.data
		self.DeleteInfo(geom)

# -------------------- End of class ModelingView



class OperationView(View):
	"""
		OperationView is a subclass of View and provides the layout
		for the HeaDSET program PathRecorder. It provides the GUI layout
		for making recordings in a project.
        """

	# -------------------- GUI Constants of OperationView
	
	FILE_OPEN   = View.FILE_EXIT + 100
	FILE_SAVE   = FILE_OPEN      + 1
	FILE_SAVEAS = FILE_SAVE      + 1
	FILE_PROP   = FILE_SAVEAS    + 1
	TOOL_POS    = FILE_PROP      + 1
	TOOL_JOY    = TOOL_POS       + 1
	TOOL_NONE   = TOOL_JOY       + 1

	VIS_PANEL   = FILE_OPEN   + 100
	HE_CHECKBOX = VIS_PANEL   + 1
	TU_CHECKBOX = HE_CHECKBOX + 1
	SP_CHECKBOX = TU_CHECKBOX + 1
	TO_CHECKBOX = SP_CHECKBOX + 1

	REC_PANEL   = VIS_PANEL  + 100
	REC_BUTTON  = REC_PANEL  + 1
	REC_LABEL   = REC_BUTTON + 1
	TIMER_LABEL = REC_LABEL  + 1

	RECINFO_TAB = REC_PANEL  + 100

	REC_BAR	    = RECINFO_TAB + 100
	PLAY_STOP   = REC_BAR     + 1
	SLIDER	    = PLAY_STOP   + 1

	def __init__(self, model):
                """
                    The constructor for OperationView initialises the layout of the frame window
                    for the HeaDSET program PathRecorder.
                    
                    @param model: The model belonging to this GUI.
                """
                
		View.__init__(self, None, -1, "PathRecorder", model)
		Publisher.subscribe(self.AddRecording, "RecordingAdded")

	# -------------------- Methods for initializing the menubar

	def InitFileMenu(self, fileMenu):
                """
                    This method (from View) is overwritten to initialize specific menu items in File menu.
                    Menu items are appended for opening and saving a project; showing properties of the current project.
                    
                    @param fileMenu: The File menu to which menu items are appended.
                """
                
		openMI   = wx.MenuItem(fileMenu, OperationView.FILE_OPEN  , "&Open Project...\tCtrl+O")
		saveMI   = wx.MenuItem(fileMenu, OperationView.FILE_SAVE  , "&Save Project\tCtrl+S")
		saveAsMI = wx.MenuItem(fileMenu, OperationView.FILE_SAVEAS, "Save Project &As...\tCtrl+Shift+S")
		propMI   = wx.MenuItem(fileMenu, OperationView.FILE_PROP  , "&Properties Project...\tCtrl+D")
		
		fileMenu.AppendItem(openMI)
		fileMenu.AppendSeparator()
		fileMenu.AppendItem(saveMI)
		fileMenu.AppendItem(saveAsMI)
		fileMenu.AppendSeparator()
		fileMenu.AppendItem(propMI)

	def InitOtherMenus(self, menuBar):
                """
                    This method (from View) is overwritten to initialize other menus
                    and their menu items between the File menu and the ViewPorts menu.
                    An Input Tool menu is appended to the menubar for selecting
                    an input tool to make recordings.
                    
                    @param menuBar: The menubar to which new menus are appended.
                """
                
		self.__toolMenu = wx.Menu()

		self.__toolMenu.AppendRadioItem(OperationView.TOOL_NONE, "&None")
		self.__toolMenu.AppendRadioItem(OperationView.TOOL_POS , "&Position instrument")
		self.__toolMenu.AppendRadioItem(OperationView.TOOL_JOY , "&Joystick controller")

		menuBar.Append(self.__toolMenu, "&Input tool")

	# -------------------- Methods for initializing the user input panel

	def InitUserInput(self, inputPanel):
                """
                    This method (from View) is overwritten to initialize the user input panel.
                    This panel has two tabs:
                        - one with panels for showing or hiding components in the model
                          and recording the path that the tool has made (named Recording Tools).
                        - one for showing information about the recordings (named Recordings Information).
                    
                    @param inputPanel: The panel to which a sizer and other widgets are attached.
                """
                
		inputSizer = wx.BoxSizer(wx.VERTICAL)
		inputNotebook = wx.Notebook(inputPanel, -1, style = wx.NB_LEFT)
		tabIcons = wx.ImageList(20, 20)
		tabIcons.AddIcon(wx.Icon("res/RecTool.png", wx.BITMAP_TYPE_PNG))
		tabIcons.AddIcon(wx.Icon("res/RecInfo.png", wx.BITMAP_TYPE_PNG))
		inputNotebook.AssignImageList(tabIcons)
		
		tabRecUserInput = self.InitRecUserInputTab(inputNotebook)
		tabRecInfo = self.InitRecInfoTab(inputNotebook)

		inputNotebook.AddPage(tabRecUserInput, "Recording Tools"       , True , 0)
		inputNotebook.AddPage(tabRecInfo     , "Recordings Information", False, 1)

		inputSizer.Add(inputNotebook, 1, wx.EXPAND)
		inputPanel.SetSizer(inputSizer)

	# -------------------- Methods for initializing the recording user input tab (part of user input panel)

	def InitRecUserInputTab(self, parent):
                """
                    This method initializes the recording user input tab (tab in user input panel named Recording Tools).
                    This panel contains the following three panels:
                        - a panel for showing or hiding components in the model.
                        - a panel for starting and stopping a recording.
                        - a panel for showing explanations how to use the two panels mentioned above.
                    
                    @param parent: The parent window to which the newly created panel belongs.
                    @return: wx.Panel object, the newly created panel.
                """
                
		tabPanel = wx.Panel(parent)
		siPanel  = self.InitVisInputPanel(tabPanel)
		riPanel  = self.InitRecInputPanel(tabPanel)
		expPanel = self.InitExplanationPanel(tabPanel)

		tabSizer = wx.BoxSizer(wx.VERTICAL)
		tabSizer.Add(siPanel , 0, wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, 5)
		tabSizer.Add(riPanel , 0, wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, 5)
		tabSizer.Add(expPanel, 1, wx.EXPAND | wx.ALL, 5)
		tabPanel.SetSizer(tabSizer)
		return tabPanel

	def InitVisInputPanel(self, parent):
                """
                    This method initializes the visibility input panel (named Visible objects)
                    inside the recording user input tab (tab in user input panel named Recording Tools).
                    It contains four checkboxes for showing or hiding the head, tumor, safe path and tool.
                    
                    @param parent: The parent window to which the newly created panel belongs.
                    @return: wx.Panel object, the newly created panel.
                """
                
		visInputPanel = wx.Panel(parent, OperationView.VIS_PANEL)
		visInputSizer = wx.StaticBoxSizer(wx.StaticBox(visInputPanel, -1, "Visible objects"), wx.VERTICAL)

		checkHead  = wx.CheckBox(visInputPanel, OperationView.HE_CHECKBOX, " Head"     , size = (View.L_BUTTON_WIDTH, -1))
		checkTumor = wx.CheckBox(visInputPanel, OperationView.TU_CHECKBOX, " Tumor"    , size = (View.L_BUTTON_WIDTH, -1))
		checkSafe  = wx.CheckBox(visInputPanel, OperationView.SP_CHECKBOX, " Safe path", size = (View.L_BUTTON_WIDTH, -1))
		checkTool  = wx.CheckBox(visInputPanel, OperationView.TO_CHECKBOX, " Tool"     , size = (View.L_BUTTON_WIDTH, -1))
		checkHead.SetValue(True)
		checkTumor.SetValue(True)
		checkSafe.SetValue(True)
		checkTool.SetValue(True)

		visInputSizer.Add(checkHead , 0, wx.LEFT | wx.TOP | wx.BOTTOM, 2)
		visInputSizer.Add(checkTumor, 0, wx.LEFT | wx.BOTTOM, 2)
		visInputSizer.Add(checkSafe , 0, wx.LEFT | wx.BOTTOM, 2)
		visInputSizer.Add(checkTool , 0, wx.LEFT | wx.BOTTOM, 2)
		visInputPanel.SetSizer(visInputSizer)
		return visInputPanel

	def InitRecInputPanel(self, parent):
                """
                    This method initializes the recording input panel (named Recording)
                    inside the recording user input tab (tab in user input panel named Recording Tools).
                    It contains a recording button and two labels for showing the recording name and timer.
                    
                    @param parent: The parent window to which the newly created panel belongs.
                    @return: wx.Panel object, the newly created panel.
                """
                
		recInputPanel = wx.Panel(parent, OperationView.REC_PANEL)
		recInputSizer = wx.StaticBoxSizer(wx.StaticBox(recInputPanel, -1, "Recording"), wx.VERTICAL)
		
		recordingButton = wx.Button(recInputPanel, OperationView.REC_BUTTON, "Start recording", size = (View.L_BUTTON_WIDTH, -1))

		recordingText = wx.StaticText(recInputPanel, OperationView.REC_LABEL, size = (View.L_BUTTON_WIDTH, -1), style = wx.ALIGN_CENTER)
		recordingText.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL))
		recordingText.SetForegroundColour("WHITE")
		
		timerText = wx.StaticText(recInputPanel, OperationView.TIMER_LABEL, "00 : 00 : 00", size = (View.L_BUTTON_WIDTH, -1), style = wx.ALIGN_CENTER)
		timerText.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL))
		timerText.SetBackgroundColour("BLACK")
		timerText.SetForegroundColour("WHITE")

		recInputSizer.Add(recordingButton, 0, wx.EXPAND)
		recInputSizer.Add(recordingText  , 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 2)
		recInputSizer.Add(timerText      , 0, wx.EXPAND)
		recInputPanel.SetSizer(recInputSizer)
		return recInputPanel

	# -------------------- Methods for initializing the recording info tab (part of user input panel)

	def InitRecInfoTab(self, parent):
                """
                    This method initializes the recording info tab (tab in user input panel named Recordings Information).
                    This panel initially contains an empty layout, showing a column where the recordings will be appended.
                    
                    @param parent: The parent window to which the newly created panel belongs.
                    @return: wx.Panel object, the newly created panel.
                """
                
		infoPanel = wx.ScrolledWindow(parent, OperationView.RECINFO_TAB, style = wx.VSCROLL)
		infoSizer = wx.BoxSizer(wx.VERTICAL)

		rLabel = wx.StaticText(infoPanel, -1, "Recordings", size = (View.L_BUTTON_WIDTH, -1), style = wx.ALIGN_CENTER)
		infoSizer.Add(rLabel, 0, wx.ALIGN_CENTER | wx.BOTTOM, 5)

		infoPanel.SetSizer(infoSizer)
		infoPanel.SetScrollRate(20, 20)
		return infoPanel

	# -------------------- Methods for updating the frame window (after user input)

	def ShowRecName(self, recId):
                """
                    This method updates the label with the name of the recording in the panel named Recording.
                    
                    @param recId: The recording number.
                """
                
                recLabel = self.FindWindowById(OperationView.REC_LABEL)

                recName = ""
                bgColour = wx.NullColour
                if recId > 0:
                    recName = "Recording " + str(recId)
                    bgColour = "NAVY"
                    self.SettingStatusBar("Busy recording...", "")
                recLabel.SetBackgroundColour(bgColour)
                recLabel.SetLabel(recName)
                recLabel.GetContainingSizer().Layout()

	def UpdateTimer(self, time):
                """
                    This method updates the label showing the timer in the panel named Recording.
                    
                    @param time: The time to be shown.
                """
                
		timerLabel = self.FindWindowById(OperationView.TIMER_LABEL)
		timerLabel.SetLabel(time)
		timerLabel.GetContainingSizer().Layout()

	def EnableMenu(self, enable):
                """
                    This method enables or disables the menuitems under the menu Input Tools.
                    
                    @param enable: The parameter indicating if the menu items should be enabled or disabled.
                """
                
		self.__toolMenu.Enable(OperationView.TOOL_NONE, enable)
		self.__toolMenu.Enable(OperationView.TOOL_POS , enable)
		self.__toolMenu.Enable(OperationView.TOOL_JOY , enable)
	
	def InsertRecording(self, recording, id):
                """
                    This method appends a new recording in the tab Recordings Information.
                    
                    @param recording: The recording to be appended.
                    @param id: The recording number.
                """
                
		tabPanel = self.FindWindowById(OperationView.RECINFO_TAB)
		tabSizer = tabPanel.GetSizer()

		duration = "Duration: " + str(int(recording.CalculateLength())) + " s"
		labelText = " Recording " + str(id + 1) + "\n " + duration
		newLabel = wx.StaticText(tabPanel, -1, labelText, size = (View.L_BUTTON_WIDTH, -1), style = wx.BORDER_SIMPLE, name = str(id))
		newLabel.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL))
		tabSizer.Add(newLabel, 0, wx.ALIGN_CENTER | wx.LEFT, 5)
		tabSizer.Layout()
		tabPanel.FitInside()

        # -------------------- Publisher-Subscriber methods
        
	def AddRecording(self, message):
                """
                    Method indicating that a recording has been added to the model and is
                    used to update this View accordingly.
                    
                    @param message: The data provided to this View to make the necessary changes possible.
                """
                
		record, recId = message.data
		self.InsertRecording(record, recId)

# -------------------- End of class OperationView



class PlayerView(View):
	"""
		PlayerView is a subclass of View and provides the layout
		for the HeaDSET program PathPlayer. It provides the GUI layout
		for playing back a recording in a project.
        """

	# -------------------- GUI Constants of PlayerView
	
	FILE_OPEN   = View.FILE_EXIT + 100
	FILE_SAVE   = FILE_OPEN      + 1
	FILE_SAVEAS = FILE_SAVE      + 1
	FILE_PROP   = FILE_SAVEAS    + 1

	VIS_PANEL   = FILE_OPEN   + 100
	HE_CHECKBOX = VIS_PANEL   + 1
	TU_CHECKBOX = HE_CHECKBOX + 1
	SP_CHECKBOX = TU_CHECKBOX + 1

	REC_PANEL   = VIS_PANEL   + 100
	REC_LABEL   = REC_PANEL   + 1

	RECINFO_TAB = REC_PANEL   + 100

	REC_BAR	    = RECINFO_TAB + 100
	PLAY_STOP   = REC_BAR     + 1
	SLIDER	    = PLAY_STOP   + 1

	def __init__(self, model):
                """
                    The constructor for PlayerView initialises the layout of the frame window
                    for the HeaDSET program PathPlayer.
                    
                    @param model: The model belonging to this GUI.
                """
                
		View.__init__(self, None, -1, "PathPlayer", model)
		self.lastSelectedLabel = None
		Publisher.subscribe(self.AddRecording, "RecordingAdded") 

	# -------------------- Methods for initializing the menubar

	def InitFileMenu(self, fileMenu):
                """
                    This method (from View) is overwritten to initialize specific menu items in File menu.
                    Menu items are appended for opening a project and showing properties of the current project.
                    
                    @param fileMenu: The File menu to which menu items are appended.
                """
                
		openMI = wx.MenuItem(fileMenu, PlayerView.FILE_OPEN, "&Open Project...\tCtrl+O")
		propMI = wx.MenuItem(fileMenu, PlayerView.FILE_PROP, "&Properties Project...\tCtrl+D")
		
		fileMenu.AppendItem(openMI)
		fileMenu.AppendSeparator()
		fileMenu.AppendItem(propMI)

	# -------------------- Methods for initializing the user input panel

	def InitUserInput(self, inputPanel):
                """
                    This method (from View) is overwritten to initialize the user input panel.
                    This panel has two tabs:
                        - one with panels for showing or hiding components in the model (named Player Tools).
                        - one for showing information about the recordings (named Recordings Information).
                    
                    @param inputPanel: The panel to which a sizer and other widgets are attached.
                """
                
		inputSizer = wx.BoxSizer(wx.VERTICAL)
		inputNotebook = wx.Notebook(inputPanel, -1, style = wx.NB_LEFT)
		tabIcons = wx.ImageList(20, 20)
		tabIcons.AddIcon(wx.Icon("res/PlayTool.png", wx.BITMAP_TYPE_PNG))
		tabIcons.AddIcon(wx.Icon("res/PlayInfo.png", wx.BITMAP_TYPE_PNG))
		inputNotebook.AssignImageList(tabIcons)
		
		tabRecUserInput = self.InitRecUserInputTab(inputNotebook)
		tabRecInfo = self.InitRecInfoTab(inputNotebook)

		inputNotebook.AddPage(tabRecUserInput, "Player Tools", True, 0)
		inputNotebook.AddPage(tabRecInfo, "Recordings Information", False, 1)

		inputSizer.Add(inputNotebook, 1, wx.EXPAND)
		inputPanel.SetSizer(inputSizer)

	# -------------------- Methods for initializing the player user input tab (part of user input panel)

	def InitRecUserInputTab(self, parent):
                """
                    This method initializes the player user input tab (tab in user input panel named Player Tools).
                    This panel contains the following two panels:
                        - a panel for showing or hiding components in the model.
                        - a panel for showing explanations how to use the two panels mentioned above.
                    
                    @param parent: The parent window to which the newly created panel belongs.
                    @return: wx.Panel object, the newly created panel.
                """
                
		tabPanel = wx.Panel(parent)
		siPanel  = self.InitVisInputPanel(tabPanel)
		rePanel  = self.InitRecInputPanel(tabPanel)
		expPanel = self.InitExplanationPanel(tabPanel)

		tabSizer = wx.BoxSizer(wx.VERTICAL)
		tabSizer.Add(siPanel , 0, wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, 5)
		tabSizer.Add(rePanel , 0, wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, 5)
		tabSizer.Add(expPanel, 1, wx.EXPAND | wx.ALL, 5)
		tabPanel.SetSizer(tabSizer)
		return tabPanel

	def InitVisInputPanel(self, parent):
                """
                    This method initializes the visibility input panel (named Visible objects)
                    inside the player user input tab (tab in user input panel named Player Tools).
                    It contains three checkboxes for showing or hiding the head, tumor and safe path.
                    
                    @param parent: The parent window to which the newly created panel belongs.
                    @return: wx.Panel object, the newly created panel.
                """
                
		visInputPanel = wx.Panel(parent, PlayerView.VIS_PANEL)
		visInputSizer = wx.StaticBoxSizer(wx.StaticBox(visInputPanel, -1, "Visible objects"), wx.VERTICAL)

		checkHead  = wx.CheckBox(visInputPanel, PlayerView.HE_CHECKBOX, "Head"     , size = (View.L_BUTTON_WIDTH, -1))
		checkTumor = wx.CheckBox(visInputPanel, PlayerView.TU_CHECKBOX, "Tumor"    , size = (View.L_BUTTON_WIDTH, -1))
		checkSafe  = wx.CheckBox(visInputPanel, PlayerView.SP_CHECKBOX, "Safe path", size = (View.L_BUTTON_WIDTH, -1))
		checkHead.SetValue(True)
		checkTumor.SetValue(True)
		checkSafe.SetValue(True)

		visInputSizer.Add(checkHead , 0, wx.LEFT | wx.TOP | wx.BOTTOM, 2)
		visInputSizer.Add(checkTumor, 0, wx.LEFT | wx.BOTTOM, 2)
		visInputSizer.Add(checkSafe , 0, wx.LEFT | wx.BOTTOM, 2)
		visInputPanel.SetSizer(visInputSizer)
		return visInputPanel

	def InitRecInputPanel(self, parent):
                """
                    This method initializes the panel (named Recording)
                    inside the player user input tab (tab in user input panel named Player Tools).
                    It contains a label with the name of the recording.
                    
                    @param parent: The parent window to which the newly created panel belongs.
                    @return: wx.Panel object, the newly created panel.
                """
                
		recInputPanel = wx.Panel(parent, PlayerView.REC_PANEL)
		recInputSizer = wx.StaticBoxSizer(wx.StaticBox(recInputPanel, -1, "Recording"), wx.VERTICAL)
		
		recordingText = wx.StaticText(recInputPanel, PlayerView.REC_LABEL, size = (View.L_BUTTON_WIDTH, -1))
		recordingText.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL))
		recordingText.SetForegroundColour("WHITE")
		recordingText.SetLabel("\n")
		
		recInputSizer.Add(recordingText, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 2)
		recInputPanel.SetSizer(recInputSizer)
		return recInputPanel

	# -------------------- Methods for initializing the player info tab (part of user input panel)

	def InitRecInfoTab(self, parent):
                """
                    This method initializes the recording info tab (tab in user input panel named Recordings Information).
                    This panel initially contains an empty layout, showing a column where the recordings will be appended.
                    
                    @param parent: The parent window to which the newly created panel belongs.
                    @return: wx.Panel object, the newly created panel.
                """
                
		infoPanel = wx.ScrolledWindow(parent, PlayerView.RECINFO_TAB, style = wx.VSCROLL)
		infoSizer = wx.BoxSizer(wx.VERTICAL)

		rLabel = wx.StaticText(infoPanel, -1, "Recordings", size = (View.L_BUTTON_WIDTH, -1), style = wx.ALIGN_CENTER)
		infoSizer.Add(rLabel, 0, wx.ALIGN_CENTER)

		infoPanel.SetSizer(infoSizer)
		infoPanel.SetScrollRate(20, 20)
		return infoPanel

	# -------------------- Methods for initializing the visualizer panel
	
	def InitBarVisualizer(self, parent):
                """
                    This method (from View) is overwritten to initialize the bar panel
                    at the bottom of the visualizer panel. It contains a button for
                    playing and stopping a recording and a playback slider.
                    
                    @param parent: The parent window to which the newly created bar panel belongs.
                    @return: wx.Panel object, the newly created bar panel.
                """
                
		barPanel = wx.Panel(parent, PlayerView.REC_BAR)
		barSizer = wx.BoxSizer(wx.HORIZONTAL)

		playButton = wx.Button(barPanel, PlayerView.PLAY_STOP, u"\u25BA", size = (30, 30))
		playButton.SetFont(wx.Font(15, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD))
		barSizer.Add(playButton)
		
		self.slider = wx.Slider(barPanel, PlayerView.SLIDER, 0, 0, 1000)
		self.slider.SetMin(0)
		self.slider.SetMax(1000)
		barSizer.Add(self.slider, 1, wx.EXPAND)
		barPanel.SetSizer(barSizer)
		return barPanel

	# -------------------- Methods for updating the frame window (after user input)
	
	def EnableRecLabel(self, selection):
                """
                    This method enables or disables the right label in player info tab named Recordings Information.
                    A previously enabled label should in this case be disabled (only one label at a time can be enabled).
                    
                    @param selection: The newly selected label.
                """
                
		if self.lastSelectedLabel == None:
			selection.SetBackgroundColour("NAVY")
			selection.SetForegroundColour("WHITE")
			self.SetRecName(selection, True)
			self.Refresh()
			self.lastSelectedLabel = selection
		elif self.lastSelectedLabel != selection:
			self.lastSelectedLabel.SetBackgroundColour("WHITE")
			self.lastSelectedLabel.SetForegroundColour("BLACK")
			selection.SetBackgroundColour("NAVY")
			selection.SetForegroundColour("WHITE")
			self.SetRecName(selection, False)
			self.Refresh()
			self.lastSelectedLabel = selection

	def SetRecName(self, selection, firstTime):
                """
                    This method sets the name of the selected recording in the panel named Recording.
                    
                    @param selection: The newly selected label.
                    @param firstTime: Parameter indicating if it is the first time that a selection is made.
                """

                recLabel = self.FindWindowById(PlayerView.REC_LABEL)
                if firstTime:
                        recLabel.SetBackgroundColour("NAVY")
                recLabel.SetLabel(selection.GetLabel())
                recLabel.Wrap(View.L_BUTTON_WIDTH)
                recLabel.GetContainingSizer().Layout()
	
	def OpenRecording(self, recording, id):
                """
                    This method shows a single recording in a project and displays them in the tab Recordings Information.
                    
                    @param recording: The recording to be opened.
                    @param id: The recording number.
                """
                
		tabPanel = self.FindWindowById(PlayerView.RECINFO_TAB)
		tabSizer = tabPanel.GetSizer()

		duration = " Duration: " + str(int(recording.CalculateLength())) + " s"
		labelText = " Recording " + str(id + 1) + "\n" + duration
		newLabel = wx.StaticText(tabPanel, -1, labelText, size = (View.L_BUTTON_WIDTH, -1), style = wx.BORDER_RAISED, name = str(id))
		newLabel.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL))
		newLabel.SetBackgroundColour("WHITE")
		tabSizer.Add(newLabel, 0, wx.ALIGN_CENTER | wx.LEFT, 5)
		tabSizer.Layout()
		tabPanel.FitInside()
		
		Publisher.sendMessage("BIND_RECLABEL", newLabel)

	# -------------------- Publisher-Subscriber methods

	def AddRecording(self, message):
                """
                    Method indicating that a project has been opened and is used to update this View accordingly.
                    
                    @param message: The data provided to this View to make the necessary changes possible.
                """
                
		record, recId = message.data
		self.OpenRecording(record, recId)

# -------------------- End of class PlayerView
